﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace Noris.Tools.TraceVisualiser.Components
{
    #region CLASS AsyncWorker : Správce fronty asynchronních požadavků (přidává požadavky, předává je ke zpracování, volá nazpátek metodu Done)
    /// <summary>
    /// Správce fronty asynchronních požadavků (přidává požadavky, předává je ke zpracování, volá nazpátek metodu Done)
    /// </summary>
    internal class AsyncWorker : IDisposable
    {
        #region SINGLETON
        private AsyncWorker()
        {
            _Initialize();
        }
        private static AsyncWorker _Instance
        {
            get
            {
                if (__Instance == null)
                {
                    lock (__Locker)
                    {
                        if (__Instance == null)
                        {
                            AsyncWorker inst = new AsyncWorker();
                            __Instance = inst;
                        }
                    }
                }
                return __Instance;
            }
        }
        private static AsyncWorker __Instance;
        private static object __Locker = new object();
        private void _Initialize()
        {
            _QueueCreate();
            _SemaphoreCreate();
            _ThreadCreate();
        }

        private void _Restart()
        {
            lock (this._AsyncQueue)
            {
                this._WorkThread.Abort();      // Zastavíme thread. On by jinak jel dál a pořád by se snažil pracovat.
                this._WorkThread = null;       // Zapomeneme na pracovní thread, on si někde v hloubi vesmíru a procesoru dožije, a dojede
                this._Initialize();            // Všechny objekty dáme nové.
            }
        }

        #endregion
		#region PUBLIC: ADD NEW REQUEST, ABORT QUEUE
		/// <summary>
		/// Zadá nový požadavek pro asynchronní zpracování. Bude zpracován, až budou dokončeny všechny předchozí požadavky.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="request"></param>
		public static void AddRequestAsync(IAsyncRequest request)
		{
			_Instance._AddRequestAsync(request);
		}
		/// <summary>
		/// <para>
		/// Zadá nový požadavek pro synchronní zpracování. 
		/// Bude zpracován okamžitě poté, kdy doběhne zpracování právě běžícího požadavku.
		/// Toto vlákno počká, až bude zdejší požadavek dokončen, a řízení vrátí hned poté (takže data v requestu budou naplněna výsledky funkce).
		/// Tento požadavek nemusí mít naplněnu metodu Done, protože výsledek dostane volající kód - ve svém threadu.
		/// Pokud bude metoda Done naplněna, zavolá se, ale v threadu Async.
		/// Tím, že se požadavek vykoná touto cestou se zajistí to, že výkonný objekt (metoda Run) bude dostávat veškeré requesty v jediném threadu,
		/// a nedojde v něm tak k multithreadingu (se všemi problémy se zabezpečením).
		/// </para>
		/// <para>
		/// POZOR: tento režim má za úkol po dokončení výkonné metody nechat request naplněný daty, volající si je převezme.
		/// Ale protože request je IDisposable, je třeba jej použít v using patternu, anebo po jeho zpracování dát ((IDisposable)request).Dispose() !!!
		/// </para>
		/// </summary>
		/// <param name="request">Požadavek na práci. Po návratu z této metody bude práce hotová, a v requestu budou platné výsledky.</param>
		public static void RunRequestSync(IAsyncRequest request)
		{
			_Instance._RunRequestSync(request);
		}
		/// <summary>
		/// Vyprázdní obsah fronty požadavků.
		/// Aktuálně běžící request doběhne, výsledek standardně předá.
		/// Async Thread nekončí, následně je možno zadávat nové requesty a budou standardně zpracovány.
		/// Tato metoda je volána v threadu GUI.
		/// </summary>
		public static void Clear()
		{
			_Instance._QueueClear();
		}
		/// <summary>
		/// Vyprázdní obsah fronty požadavků, ukončí běh Async threadu.
		/// Aktuálně běžící request doběhne, ale už se nepředá jeho výsledek (možná ani nebude kam jej předat).
		/// Žádný další požadavek nebude možno přidat.
		/// Tato metoda je volána v threadu GUI.
		/// </summary>
		public static void Abort()
		{
			_Instance._QueueAbort();
		}
		/// <summary>
		/// Zajistí restart fronty: zruší aktuální výkon, a odstartuje frontu znovu
		/// </summary>
		public static void Restart()
		{
			_Instance._QueueAbort();
			_Instance._Restart();
		}
		#endregion
		#region PROPERTY
		/// <summary>
		/// Obsahuje true, pokud je Worker připraven přijímat a zpracovávat requesty.
		/// Obsahuje false, pokud je Worker již ukončen (znovu nastartovat lze metodou Restart).
		/// </summary>
		public static bool IsReady { get { return !_Instance._Abort; } }
		#endregion
		#region PRIVATE: QUEUE
        private void _QueueCreate()
        {
            _AsyncQueue = new Queue<IAsyncRequest>();
            _SyncRequest = null;
            _LastTimeRequest = DateTime.Now;
        }
        /// <summary>
        /// Do fronty práce přidá další požadavek na asynchronní zpracování.
        /// Tato metoda je volána v threadu GUI (Owner).
        /// Po dobu přidávání zamyká frontu (aby se thread GUI nepopral s threadem na pozadí, který si z téže fronty odebírá requesty ke zpracování).
        /// </summary>
        /// <param name="request"></param>
        private void _AddRequestAsync(IAsyncRequest request)
        {
            if (request == null) return;
			this._CheckAbort();
			this._CheckRequestState(request);
			lock (this._AsyncQueue)
            {
                this._AsyncQueue.Enqueue(request);
            }
            this._ShowQueueInStatusBar();           // Zobrazíme stav fronty ve status baru
            this._WaitForRequest.Set();             // Pošleme signál do výkonného threadu, že se něco změnilo (výkonný thread může spát, tímto ho vzbudíme)
        }
        /// <summary>
        /// Zajistí přidání požadavku pro zpracování "ihned".
        /// Tato metoda je volána v threadu GUI.
        /// Po dobu přidávání zamyká frontu (aby se thread GUI nepopral s threadem na pozadí, který si z téže fronty odebírá requesty ke zpracování).
        /// Tato metoda čeká ve stavu spánku, až bude request vyřízen.
        /// </summary>
        /// <param name="request"></param>
        private void _RunRequestSync(IAsyncRequest request)
        {
            if (request == null) return;
            this._CheckAbort();
			this._CheckRequestState(request);
			lock (this._AsyncQueue)
            {
                this._SyncRequest = request;
                this._WaitForSyncDone.Reset();      // Připravíme si hlídáček, aby nám dal vědět, až bude hotovo
                this._WaitForRequest.Set();         // Pošleme signál do výkonného threadu, že se něco změnilo (výkonný thread může spát, tímto ho vzbudíme)
            }
            this._ShowQueueInStatusBar();           // Zobrazíme stav fronty ve status baru
            this._WaitForSyncDone.WaitOne();        // My jdeme spát, a až bude náš synchronní request vyřízen, tak nám správce (metoda _ThreadStart()) dá vědět.
            // Odsud se dostaneme až potom, co bude náš request vyřízen!
        }
        /// <summary>
        /// Pokud je stav Abort, pak vyvolá chybu.
        /// </summary>
        private void _CheckAbort()
        {
            if (this._Abort)
                throw new InvalidOperationException("Cannot add new request into aborted Worker.");
        }
		/// <summary>
		/// Ověří stav requestu (nesmí být Running, Done, Error), a pokud je v pořádku, pak jej nastaví jako Waiting.
		/// </summary>
		/// <param name="request"></param>
		private void _CheckRequestState(IAsyncRequest request)
		{
			if (request == null)
				throw new InvalidOperationException("Request cannot be null.");
			if (request.State == AsyncRequestState.Running || request.State == AsyncRequestState.Done || request.State == AsyncRequestState.Error)
				throw new InvalidOperationException("Request must be in state Waiting. Current request is in state = " + request.State.ToString());
			request.State = AsyncRequestState.Waiting;
			request.OwnerThread = System.Threading.Thread.CurrentThread;
		}
		/// <summary>
        /// Vyprázdní obsah fronty požadavků.
        /// Aktuálně běžící request doběhne, výsledek standardně předá.
        /// Async Thread nekončí, následně je možno zadávat nové requesty a budou standardně zpracovány.
        /// Tato metoda je volána v threadu GUI.
        /// </summary>
        private void _QueueClear()
        {
            lock (this._AsyncQueue)
            {
                this._SyncRequest = null;
                this._AsyncQueue.Clear();
            }
            this._ShowQueueInStatusBar();           // Zobrazíme stav fronty ve status baru
        }
        /// <summary>
        /// Vyprázdní obsah fronty požadavků, ukončí běh Async threadu.
        /// Aktuálně běžící request doběhne, ale už se nepředá jeho výsledek (možná ani nebude kam jej předat).
        /// Žádný další požadavek nebude možno přidat.
        /// Tato metoda je volána v threadu GUI.
        /// </summary>
        private void _QueueAbort()
        {
            this._Abort = true;
            this._QueueClear();
        }
        /// <summary>
        /// Vrátí další request. Pokud žádný není, pak si na něj počká.
        /// Tato metoda se volá v threadu Async.
        /// </summary>
        /// <returns></returns>
        private IAsyncRequest _QueueGet(out bool isSync)
        {
            this._LastTimeRequest = DateTime.Now;                     // Proč? To proto, abych volný čas počítal od doběhnutí předešlého requestu, a ne od doby jeho spuštění.
            while (true)
            {
                // Podívám se, zda ve frontě je request:
                lock (this._AsyncQueue)
                {
                    _WaitForRequest.Reset();

                    // Pokud máme synchronní request:
                    if (this._SyncRequest != null)
                    {   // Vyjmu jej z proměnné a vrátím, nastavím isSync = true
                        isSync = true;
                        IAsyncRequest request = this._SyncRequest;
                        this._SyncRequest = null;
                        this._LastTimeRequest = DateTime.Now;
                        return request;
                    }

                    // Pokud máme request, vyjmu jej z fronty a vrátím z metody, nastavím isSync = false:
                    if (this._AsyncQueue.Count > 0)
                    {
                        isSync = false;
                        this._LastTimeRequest = DateTime.Now;
                        return this._AsyncQueue.Dequeue();
                    }

                    // Nemáme co na práci. Máme určenou nějakou volnočasovou aktivitu? A skutečně už od posledního requestu uběhl předepsaný čas?
                    if (this._FreeTimeActivity != null && ((TimeSpan)(DateTime.Now - this._LastTimeRequest)).TotalSeconds >= this._FreeTimeInterval)
                    {
                        // Požádáme toho, kdo řídí naši volnočasovou aktivitu, aby nám přidělil nějakou práci (mimo pořadník, ten je prázdný):
                        IAsyncRequest freeTimeRequest = this._FreeTimeActivity();
                        if (freeTimeRequest != null)
                        {
                            isSync = false;
                            this._LastTimeRequest = DateTime.Now;
                            return freeTimeRequest;
                        }
                    }

                    // Pokud request nemáme, pak uvolním zámek a jdu spát:
                }

                // Ve frontě není nic k práci, jdu spát. Pokud není určena volnočasová aktivita, pak bez omezení, jinak na daný čas:
                int freeTimeMilisec = this._FreeTimeMilisec;
                if (this._FreeTimeActivity == null || freeTimeMilisec <= 0)
                    this._WaitForRequest.WaitOne();
                else
                    this._WaitForRequest.WaitOne(freeTimeMilisec);
                // Tady vlákno spí a čeká, až někdo nastaví this._WaitForRequest.Set(), pak odsud přejdeme dál:
                if (_Abort) break;
            }
            isSync = false;
            return null;
        }
        /// <summary>
        /// Zobrazí stav fronty do status baru.
        /// </summary>
        private void _ShowQueueInStatusBar()
        {
            string text = this._AsyncQueue.Count.ToString();
            if (this._SyncRequest != null)
                text += " + 1";
            text += " request waiting.";
            TraceVisualiser.Support.Steward.StatbusBarSet(Noris.Tools.TraceVisualiser.Forms.StatusBarItem.StatusText, text);
        }
        private DateTime _LastTimeRequest;
        private Queue<IAsyncRequest> _AsyncQueue;
        private IAsyncRequest _SyncRequest;
        #endregion
        #region Co dělat, když není co dělat - aneb nuda, nuda, nuda: nenašel by se někdo, kdo by pro mě měl nějakou práci?
        /// <summary>
        /// Zaeviduje si aktivitu, kterou může dělat tehdy, když zrovna nemá co na práci.
        /// Volnočasová aktivita může být všeho všudy jen jedna, a protože jde o singleton tak jen jedna na celou aplikaci.
        /// Vložení další aktivity přepíše aktivitu stávající; takže o volný čas tohoto AsyncWorkeru se vede tuhý boj: kdo si zadá požadavek poslední, ten je vítěz.
        /// </summary>
        /// <param name="activity">Akce, kterou má AsyncWorker zavolat, když nebude mít co dělat. Tato akce typicky vymyslí nějaký požadavek a předá jej do AsyncWorkeru.</param>
        /// <param name="freeTime">Čas neaktivity, po němž AsyncWorker usoudí že nemá co dělat [v sekundách].</param>
        public static void FreeTimeActivitySet(AsyncWorkFreeTimeActivityDelegate activity, double freeTime)
        {
            _Instance._FreeTimeActivity = activity;
            _Instance._FreeTimeInterval = freeTime;
        }
        /// <summary>
        /// Zruší volnočasovou aktivitu. Použije se v situaci, kdy provozovatel volnočasové aktivity končí svou činnost.
        /// Nefunguje zde nic jako zásobník volnočasových aktivit: po Clearu() se v volném čase nic nedělá, dokud někdo jiný nezadá svůj požadavek.
        /// </summary>
        public static void FreeTimeActivityClear()
        {
            _Instance._FreeTimeActivity = null;
            _Instance._FreeTimeInterval = 0;
        }
        private AsyncWorkFreeTimeActivityDelegate _FreeTimeActivity;
        private double _FreeTimeInterval;
        private int _FreeTimeMilisec
        {
            get
            {
                if (this._FreeTimeInterval <= 0D) return 0;
                return (int)Math.Round((1000D * this._FreeTimeInterval), 0);

            }
        }
        #endregion
        #region PRIVATE: SEMAPHORE
        private void _SemaphoreCreate()
        {
            this._WaitForRequest = new AutoResetEvent(false);
            this._WaitForRequest.Reset();
            this._WaitForSyncDone = new AutoResetEvent(false);
            this._WaitForSyncDone.Reset();
        }
        /// <summary>Event pro signalizaci příchodu nového požadavku (z GUI do Async threadu)</summary>
        private AutoResetEvent _WaitForRequest;
        /// <summary>Event pro signalizaci dokončení práce na synchronním requestu (z Async do GUI threadu)</summary>
        private AutoResetEvent _WaitForSyncDone;
        #endregion
        #region PRIVATE: THREAD (async run)
        /// <summary>
        /// Metoda je volána v threadu typicky GUI, na popředí
        /// </summary>
        private void _ThreadCreate()
        {
            this._Abort = false;
            this._WorkThread = new Thread(this._ThreadStart);
            this._WorkThread.Name = "AsyncWorker";
            this._WorkThread.IsBackground = true;
            this._WorkThread.CurrentCulture = Thread.CurrentThread.CurrentCulture;
            this._WorkThread.CurrentUICulture = Thread.CurrentThread.CurrentUICulture;
            this._WorkThread.Start();
        }
        /// <summary>
        /// Výkoná metoda threadu na pozadí, pouze v této metodě se thread zdržuje celý život.
        /// Tato metoda je vyvolána již v threadu na pozadí.
        /// </summary>
        private void _ThreadStart()
        {
			bool isSync;
			while (true)
            {
                if (this._Abort) break;
                this._CurrentRequest = _QueueGet(out isSync);     // Získám request. Metoda na něj počká, pokud hned není k dispozici. Nastaví hodnotu isSync = true, pokud jde o synchronní request.
                // Pozor, pokud jsem zde získal synchronní request, pak nyní GUI vlákno čeká na jeho dokončení a nic nedělá !!!
				if (this._Abort)
				{
					// Pokud to byl synchronní request, pak musím oznámit jeho dokončení:
					if (isSync) this._WaitForSyncDone.Set();
					break;
				}
                IAsyncRequest reqest = this._CurrentRequest;
				if (reqest == null)
				{
					// Pokud to byl synchronní request, pak musím oznámit jeho dokončení:
					if (isSync) this._WaitForSyncDone.Set();
					break;   // Abort.
				}

                bool callDone = true;
                try
                {
					if (reqest.RunMethod != null)
					{
						reqest.State = AsyncRequestState.Running;
						reqest.RunMethod(reqest);
						reqest.State = AsyncRequestState.Done;
					}
                }
                catch (System.Threading.ThreadAbortException)
                {   // Thread je násilně ukončen, nebudeme nic hlásit:
                    callDone = false;
                }
                catch (Exception exc)
                {
                    reqest = this._CurrentRequest;
					if (reqest != null)
					{
						reqest.State = AsyncRequestState.Error;
						reqest.Exc = exc;
					}
                }
                finally
                {
                    reqest = this._CurrentRequest;
                    this._ShowQueueInStatusBar();           // Zobrazíme stav fronty ve status baru
                    if (callDone && reqest != null && reqest.DoneMethod != null && !this._Abort)
                    {
                        try
                        {
                            reqest.DoneMethod(reqest);
                        }
                        catch (Exception)
                        { }
                    }
                }

				// Skončeno - zapomeňme a recyklujme:
				//  S jednou výjimkou = a to je synchronní běh, 
				//  když volající metoda očekává, že po skončení dostane platná data s výsledky funkce !
				//  Je na této metodě, aby request recyklovala.
				reqest = this._CurrentRequest;
                this._CurrentRequest = null;
				if (reqest != null && !isSync)
					reqest.Dispose();

                // Pokud to byl synchronní request, pak musím oznámit jeho dokončení:
                if (isSync)
                    this._WaitForSyncDone.Set();

                if (this._Abort) break;
            }
            this._ShowQueueInStatusBar();           // Zobrazíme stav fronty ve status baru
        }
        /// <summary>
        /// Thread na pozadí, ve kterém se vykonává veškerá práce
        /// </summary>
        private Thread _WorkThread;
        /// <summary>
        /// Příznak požadavku na zastavení a zrušení fronty práce
        /// </summary>
        private bool _Abort;
        /// <summary>
        /// Úložiště aktuálně zpracovávaného requestu
        /// </summary>
        private IAsyncRequest _CurrentRequest;
        #endregion
        #region IDisposable Members
        void IDisposable.Dispose()
        {
            this._Abort = true;
        }

        #endregion
    }
    #endregion
	#region INTERFACE IAsyncRequest : předpis pro třídy, které mohou předávat data do asynchronních metod. ENUM AsyncRequestState : stav požadavku
	/// <summary>
    /// IAsyncRequest : předpis pro třídy, které mohou předávat data do asynchronních metod
    /// </summary>
    public interface IAsyncRequest : IDisposable
    {
		/// <summary>
		/// Metoda, která je volána jako výkonná metoda požadavku.
		/// Předává se jí jako parametr tento request.
		/// </summary>
        Action<object> RunMethod { get; }
		/// <summary>
		/// Metoda, která je volána po dokončení zpracování, ať v pořádku nebo s chybou (viz State)
		/// Předává se jí jako parametr tento request.
		/// </summary>
        Action<object> DoneMethod { get; set; }
		/// <summary>
		/// Chyba, ke které došlo v průběhu zpracování
		/// </summary>
        Exception Exc { get; set; }
		/// <summary>
		/// Stav tohoto požadavku
		/// </summary>
		AsyncRequestState State { get; set; }
		Thread OwnerThread { get; set; }
    }
    /// <summary>
    /// Předpis pro metodu, kterou může AsyncWorker zavolat v situaci, kdy delší dobu nemá co na práci.
    /// </summary>
    /// <returns></returns>
    public delegate IAsyncRequest AsyncWorkFreeTimeActivityDelegate();
	/// <summary>
	/// Stavy požadavku z hlediska zpracování
	/// </summary>
	public enum AsyncRequestState
	{
        /// <summary>
        /// Požadavek se připravuje, ještě není předán ke zpracování do fronty AsyncWorker
        /// </summary>
		None = 0,
        /// <summary>
        /// Požadavek čeká na zpracování
        /// </summary>
		Waiting,
        /// <summary>
        /// Požadavek se zpracovává
        /// </summary>
		Running,
        /// <summary>
        /// Požadavek je dokončen bez chyb
        /// </summary>
		Done,
        /// <summary>
        /// Požadavek skončil chybou, ta je uložena v requestu
        /// </summary>
		Error,
        /// <summary>
        /// Požadavek skončil a je uvolněn z paměti
        /// </summary>
		Disposed
	}
    #endregion
}
